﻿using System;
using System.Collections.Generic;
using Godot.core;

namespace Godot.modules.gdscript
{
    public partial class GDParser
    {
        #region ____inner_types____
        private enum DictExpect
        {
            DICT_EXPECT_KEY,
            DICT_EXPECT_COLON,
            DICT_EXPECT_VALUE,
            DICT_EXPECT_COMMA
        }

        public class Node
        {
            public enum Type
            {
                TYPE_CLASS,
                TYPE_FUNCTION,
                TYPE_BUILT_IN_FUNCTION,
                TYPE_BLOCK,
                TYPE_IDENTIFIER,
                TYPE_TYPE,
                TYPE_CONSTANT,
                TYPE_ARRAY,
                TYPE_DICTIONARY,
                TYPE_SELF,
                TYPE_OPERATOR,
                TYPE_CONTROL_FLOW,
                TYPE_LOCAL_VAR,
                TYPE_ASSERT,
                TYPE_NEWLINE
            }

            public Node next { get; set; }
            public Node.Type type { get; set; }
            public int line { get; set; }
            public int column { get; set; }


            protected Node(){}
        }
          
        public class ClassNode : Node
        {
            public bool tool;
            public StringName name;
            public bool extends_used;
            public StringName extends_file;
            public List<StringName> extends_class;

            public struct Member
            {
                public PropertyInfo export;
                public StringName identifier;
            }

            public struct Constant
            {
                public StringName identifier;
                public Node expression;
            }

            public List<ClassNode> subclasses;
            public List<Member> variables;
            public List<Constant> constant_expressions;
            public List<FunctionNode> functions;
            public List<FunctionNode> static_functions;
            public BlockNode initializer;

            public int end_line;

            public ClassNode()
            {
                tool = false;
                type = Node.Type.TYPE_CLASS;
                extends_used = false;
                end_line = -1;
            }
        }

        public class FunctionNode : Node
        {
            public bool is_static;
            public StringName name;
            public List<StringName> arguments;
            public List<Node> default_values;
            public BlockNode body;

            public FunctionNode()
            {
                type = Node.Type.TYPE_FUNCTION;
                is_static = false;
            }
        }

        public class BlockNode : Node
        {
            public Dictionary<StringName, int> locals;
            public List<Node> statements;
            public List<StringName> variables;
            public List<int> variable_lines;

            //the following is useful for code completion
            public List<BlockNode> sub_blocks;
            public int end_line;
            public BlockNode()
            {
                type = Node.Type.TYPE_BLOCK;
                end_line = -1;
            }
        }

        public class TypeNode : Node
        {
            public Variant.Type vtype;

            public TypeNode()
            {
                type = Node.Type.TYPE_TYPE;
            }
        }

        public class BuiltInFunctionNode : Node
        {
            public GDFunctions.Function function;

            public BuiltInFunctionNode()
            {
                type = Node.Type.TYPE_BUILT_IN_FUNCTION;
            }
        }

        public class IdentifierNode : Node
        {
            public StringName name;

            public IdentifierNode()
            {
                type = Node.Type.TYPE_IDENTIFIER;
            }
        }

        public class LocalVarNode : Node
        {
            public StringName name;
            public Node assign;
            public LocalVarNode()
            {
                type = Node.Type.TYPE_LOCAL_VAR;
                assign = null;
            }
        }

        public class ConstantNode : Node
        {
            public Variant value;

            public ConstantNode()
            {
                type = Node.Type.TYPE_CONSTANT;
            }
        }

        public class ArrayNode : Node
        {
            public List<Node> elements;

            public ArrayNode()
            {
                type = Node.Type.TYPE_ARRAY;
            }
        }

        public class DictionaryNode : Node
        {
            public List<KeyValuePair<Node, Node>> elements;
            public DictionaryNode()
            {
                type = Node.Type.TYPE_DICTIONARY;
            }
        }

        public class SelfNode : Node
        {
            public SelfNode()
            {
                type = Node.Type.TYPE_SELF;
            }
        }

        public class OperatorNode : Node
        {
            public enum Operator
            {
                //call/constructor operator
                OP_CALL,
                OP_PARENT_CALL,
                OP_EXTENDS,
                //indexing operator
                OP_INDEX,
                OP_INDEX_NAMED,
                //unary operators
                OP_NEG,
                OP_NOT,
                OP_BIT_INVERT,
                OP_PREINC,
                OP_PREDEC,
                OP_INC,
                OP_DEC,
                //binary operators (in precedence order)
                OP_IN,
                OP_EQUAL,
                OP_NOT_EQUAL,
                OP_LESS,
                OP_LESS_EQUAL,
                OP_GREATER,
                OP_GREATER_EQUAL,
                OP_AND,
                OP_OR,
                OP_ADD,
                OP_SUB,
                OP_MUL,
                OP_DIV,
                OP_MOD,
                OP_SHIFT_LEFT,
                OP_SHIFT_RIGHT,
                OP_ASSIGN,
                OP_ASSIGN_ADD,
                OP_ASSIGN_SUB,
                OP_ASSIGN_MUL,
                OP_ASSIGN_DIV,
                OP_ASSIGN_MOD,
                OP_ASSIGN_SHIFT_LEFT,
                OP_ASSIGN_SHIFT_RIGHT,
                OP_ASSIGN_BIT_AND,
                OP_ASSIGN_BIT_OR,
                OP_ASSIGN_BIT_XOR,
                OP_BIT_AND,
                OP_BIT_OR,
                OP_BIT_XOR
            }

            public Operator op;

            public List<Node> arguments;

            public OperatorNode()
            {
                type = Node.Type.TYPE_OPERATOR;
            }
        }

        public class ControlFlowNode : Node
        {
            public enum CFType
            {
                CF_IF,
                CF_FOR,
                CF_WHILE,
                CF_SWITCH,
                CF_BREAK,
                CF_CONTINUE,
                CF_RETURN
            };

            public CFType cf_type;
            public List<Node> arguments;
            public BlockNode body;
            public BlockNode body_else;

            public ControlFlowNode else_part; //used for if

            public ControlFlowNode()
            {
                type = Node.Type.TYPE_CONTROL_FLOW;
                cf_type = CFType.CF_IF;
                body = body_else = null;
            }
        }

        public class AssertNode : Node
        {
            public Node condition;
            public AssertNode()
            {
                type = Node.Type.TYPE_ASSERT;
            }
        };

        public class NewLineNode : Node
        {
            public int line;
            public NewLineNode()
            {
                type = Node.Type.TYPE_NEWLINE;
            }
        }

        public class Expression
        {
            public bool is_op;

            public OperatorNode.Operator op;
            public Node node;
        }
        #endregion

        private Node _alloc_node(Node.Type p_type)
        {
            GDParser.Node t = null;

            switch (p_type)
            {
                #region ____types_cases____
                case Node.Type.TYPE_CLASS:
                    {
                        t = new GDParser.ClassNode();
                    } break;

                case Node.Type.TYPE_FUNCTION:
                    {
                        t = new GDParser.FunctionNode();
                    } break;

                case Node.Type.TYPE_BUILT_IN_FUNCTION:
                    {
                        t = new GDParser.BuiltInFunctionNode();
                    } break;

                case Node.Type.TYPE_BLOCK:
                    {
                        t = new GDParser.BlockNode();
                    } break;

                case Node.Type.TYPE_IDENTIFIER:
                    {
                        t = new GDParser.IdentifierNode();
                    } break;

                case Node.Type.TYPE_TYPE:
                    {
                        t = new GDParser.TypeNode();
                    } break;

                case Node.Type.TYPE_CONSTANT:
                    {
                        t = new GDParser.ConstantNode();
                    } break;

                case Node.Type.TYPE_ARRAY:
                    {
                        t = new GDParser.ArrayNode();
                    } break;

                case Node.Type.TYPE_DICTIONARY:
                    {
                        t = new GDParser.DictionaryNode();
                    } break;

                case Node.Type.TYPE_SELF:
                    {
                        t = new GDParser.SelfNode();
                    } break;

                case Node.Type.TYPE_OPERATOR:
                    {
                        t = new GDParser.OperatorNode();
                    } break;

                case Node.Type.TYPE_CONTROL_FLOW:
                    {
                        t = new GDParser.ControlFlowNode();
                    } break;

                case Node.Type.TYPE_LOCAL_VAR:
                    {
                        t = new GDParser.LocalVarNode();
                    } break;

                case Node.Type.TYPE_ASSERT:
                    {
                        t = new GDParser.AssertNode();
                    } break;

                case Node.Type.TYPE_NEWLINE:
                    {
                        t = new GDParser.NewLineNode();
                    } break;
                #endregion
            }

            t.next = _list;
            _list = t;

            if (_head == null)
                _head = t;

            t.line = _tokenizer.get_token_line();
            t.column = _tokenizer.get_token_column();
            return t;
        }

        private void _set_error(string p_error, int p_line = -1, int p_column = -1)
        {
            if (_error_set)
                return; //allow no further errors

            _error = p_error;
            _error_line = p_line < 0 ? _tokenizer.get_token_line() : p_line;
            _error_column = p_column < 0 ? _tokenizer.get_token_column() : p_column;
            _error_set = true;
        }

        private bool _enter_indent_block(BlockNode p_block = null)
        {
            if (_tokenizer.get_token() != GDTokenizer.Token.TK_COLON)
            {
                _set_error("':' expected at end of line.");
                return false;
            }
            _tokenizer.advance();

            if (_tokenizer.get_token() != GDTokenizer.Token.TK_NEWLINE)
            {
                _set_error("newline expected after ':'.");
                return false;
            }

            while (true)
            {
                if (_tokenizer.get_token() != GDTokenizer.Token.TK_NEWLINE)
                {
                    return false; //wtf
                }
                else if (_tokenizer.get_token(1) != GDTokenizer.Token.TK_NEWLINE)
                {
                    int indent = _tokenizer.get_token_line_indent();
                    int current = _tab_level[_tab_level.Count - 1];
                    if (indent <= current)
                        return false;

                    _tab_level.Add(indent);
                    _tokenizer.advance();
                    return true;

                }
                else if (p_block != null)
                {
                    NewLineNode nl = (NewLineNode)_alloc_node(Node.Type.TYPE_NEWLINE);
                    nl.line = _tokenizer.get_token_line();
                    p_block.statements.Add(nl);

                }
                _tokenizer.advance(); // go to next newline
            }
        }

        private bool _end_statement()
        {
            if (_tokenizer.get_token() == GDTokenizer.Token.TK_SEMICOLON)
            {
                _tokenizer.advance();
                return true; //handle next
            }
            else if (_tokenizer.get_token() == GDTokenizer.Token.TK_NEWLINE || _tokenizer.get_token() == GDTokenizer.Token.TK_EOF)
            {
                return true; //will be handled properly
            }

            return false;
        }

        private bool _parse_arguments(Node p_parent, List<Node> p_args, bool p_static)
        {
            if (_tokenizer.get_token() == GDTokenizer.Token.TK_PARENTHESIS_CLOSE)
            {
                _tokenizer.advance();
            }
            else
            {
                while (true)
                {
                    Node arg = _parse_expression(p_parent, p_static);
                    if (arg == null)
                        return false;

                    p_args.Add(arg);

                    if (_tokenizer.get_token() == GDTokenizer.Token.TK_PARENTHESIS_CLOSE)
                    {
                        _tokenizer.advance();
                        break;
                    }
                    else if (_tokenizer.get_token() == GDTokenizer.Token.TK_COMMA)
                    {

                        if (_tokenizer.get_token(1) == GDTokenizer.Token.TK_PARENTHESIS_CLOSE)
                        {
                            _set_error("Expression expected");
                            return false;
                        }

                        _tokenizer.advance();
                    }
                    else
                    {
                        // something is broken
                        _set_error("Expected ',' or ')'");
                        return false;
                    }
                }
            }
            return true;
        }

        private void _parse_extends(ClassNode p_class)
        {
            if (p_class.extends_used)
            {
                _set_error("'extends' already used for this class.");
                return;
            }

            if (p_class.constant_expressions.Count == 0 || p_class.subclasses.Count == 0 || p_class.functions.Count == 0 || p_class.variables.Count == 0)
            {
                _set_error("'extends' must be used before anything else.");
                return;
            }

            p_class.extends_used = true;

            //see if inheritance happens from a file
            _tokenizer.advance();

            if (_tokenizer.get_token() == GDTokenizer.Token.TK_CONSTANT)
            {
                Variant constant = _tokenizer.get_token_constant();
                if (constant.type != Variant.Type.STRING)
                {
                    _set_error("'extends' constant must be a string.");
                    return;
                }

                p_class.extends_file = constant;
                _tokenizer.advance();

                if (_tokenizer.get_token() != GDTokenizer.Token.TK_PERIOD)
                {
                    return;
                }
                else
                {
                    _tokenizer.advance();
                }
            }

            while (true)
            {
                if (_tokenizer.get_token() != GDTokenizer.Token.TK_IDENTIFIER)
                {
                    _set_error("Invalid 'extends' syntax, expected string constant (path) and/or identifier (parent class).");
                    return;
                }

                StringName identifier = _tokenizer.get_token_identifier();
                p_class.extends_class.Add(identifier);

                _tokenizer.advance(1);
                if (_tokenizer.get_token() != GDTokenizer.Token.TK_PERIOD)
                    return;
            }
        }

        private bool _parse_newline()
        {
            if (_tokenizer.get_token(1) != GDTokenizer.Token.TK_EOF && _tokenizer.get_token(1) != GDTokenizer.Token.TK_NEWLINE)
            {
                int indent = _tokenizer.get_token_line_indent();
                int current_indent = _tab_level[_tab_level.Count - 1];

                if (indent > current_indent)
                {
                    _set_error("Unexpected indent.");
                    return false;
                }

                if (indent < current_indent)
                {
                    while (indent < current_indent)
                    {
                        //exit block
                        if (_tab_level.Count == 1)
                        {
                            _set_error("Invalid indent. BUG?");
                            return false;
                        }

                        _tab_level.RemoveAt(_tab_level.Count - 1); ;

                        if (_tab_level[_tab_level.Count - 1] < indent)
                        {
                            _set_error("Unindent does not match any outer indentation level.");
                            return false;
                        }
                        current_indent = _tab_level[_tab_level.Count - 1];
                    }
                    _tokenizer.advance();
                    return false;
                }
            }
            _tokenizer.advance();
            return true;
        }       
    }
}
